// power of for unsigned integers
owl_int_op_result owl_pow_uint(owl_umax base, owl_byte exponent)
{
  // check params
  if (base == 0 && exponent == 0)
    return (owl_int_op_result) {{0}, owl_false};
  else if (base == 0)
    return (owl_int_op_result) {{0}, owl_true};
  else if (base != 0 && exponent == 0)
    return (owl_int_op_result) {{1}, owl_true};
    
  // calculate the result
  owl_umax result = base;
  while (--exponent > 0) result *= base;
  return (owl_int_op_result) {{result}, owl_true};
}

// power of for signed integers
owl_int_op_result owl_pow_sint(owl_smax base, owl_byte exponent)
{
  owl_umax new_base = base < 0 ? -base : base;
  owl_int_op_result rtn = owl_pow_uint(new_base, exponent);
  rtn.integer.s = ((base < 0) && (exponent % 2) != 0 ? -1 : 1) * ((owl_smax) rtn.integer.u);
  return rtn;
}

// get max unsigned integer
owl_int_op_result owl_get_max_uint(owl_umax uint1, owl_umax uint2)
{
  return ((uint1 > uint2) ?
          (owl_int_op_result) {{uint1}, owl_true} : (owl_int_op_result) {{uint2}, owl_true});
}

// get min unsigned integer
owl_int_op_result owl_get_min_uint(owl_umax uint1, owl_umax uint2)
{
  return ((uint1 < uint2) ?
          (owl_int_op_result) {{uint1}, owl_true} : (owl_int_op_result) {{uint2}, owl_true});
}

// calculate bits as unsigned integer
owl_int_op_result owl_bits_as_uint(owl_bitarr * bits)
{
  // check params
  if (owl_check_bitarr_all(bits, sizeof(owl_bitarr)) == owl_false)
    return (owl_int_op_result) {{0}, owl_false};
  
  // get the bitarr extra variables
  struct owl_bitarr_extra_vars a = owl_get_bitarr_extra_vars(bits);
  owl_umax result = 0, pow2 = 1;
  // calculate the number
  owl_byte last_bit_mask = 1 << (OWL_BYTE_BITS - 1 - a.last_bit_offset);
  for (owl_umax i = bits -> num_bits; i > 0; i--, pow2 <<= 1, owl_circ_bitshift(bits, OWL_BITSHIFT_RIGHT))
    result += (((bits -> arr -> ptr)[a.last_byte_pos] & last_bit_mask) != 0) ? pow2 : 0;
  return (owl_int_op_result) {{result}, owl_true};
}

// calculate bits as sign/magnitude integer
owl_int_op_result owl_bits_as_sign_mag_sint(owl_bitarr * bits)
{
  // check params
  if (owl_check_bitarr_all(bits, sizeof(owl_bitarr)) == owl_false)
    return (owl_int_op_result) {{0}, owl_false};
  
  // get the bitarr extra variables
  struct owl_bitarr_extra_vars a = owl_get_bitarr_extra_vars(bits);
  // modify the bitarr so it points to all the bits excluding the first one
  bits -> left_pad = (bits -> left_pad) == (OWL_BYTE_BITS - 1) ? 0 : bits -> left_pad + 1;
  (bits -> num_bits)--; 
  // reuse owl_bits_as_unit()
  owl_smax result = (owl_smax) owl_bits_as_uint(bits).integer.u;
  owl_byte first_bit_mask = 1 << (OWL_BYTE_BITS - 1 - a.first_bit_offset);
  // consider the sign
  result = (bits -> arr -> ptr)[a.first_byte_pos] & first_bit_mask ? -result : result;
  // reverse the changes made to bitarr
  bits -> left_pad = (bits -> left_pad) == 0 ? OWL_BYTE_BITS - 1 : bits -> left_pad - 1;
  (bits -> num_bits)++;
  return (owl_int_op_result) {{result}, owl_true};
}

// calculate bits as 1 complement integer
owl_int_op_result owl_bits_as_1_compl_sint(owl_bitarr * bits)
{
  // check params
  if (owl_check_bitarr_all(bits, sizeof(owl_bitarr)) == owl_false)
    return (owl_int_op_result) {{0}, owl_false};
  
  // I can actually just reuse owl_bits_as_sign_mag_sint()
  owl_smax result = owl_bits_as_sign_mag_sint(bits).integer.s;
  if (result < 0) {
    // fast power of 2
    owl_umax pow2 = 1;
    for (owl_umax i = 0; i < bits -> num_bits - 1; i++) pow2 <<= 1;
    result = (-result) - (pow2 - 1);
  }
  return (owl_int_op_result) {{result}, owl_true};
}

// calculate bits as 2 complement integer
owl_int_op_result owl_bits_as_2_compl_sint(owl_bitarr * bits)
{
  // check params
  if (owl_check_bitarr_all(bits, sizeof(owl_bitarr)) == owl_false)
    return (owl_int_op_result) {{0}, owl_false};
  
  // I can actually just reuse owl_bits_as_1_compl_sint()
  owl_smax result = owl_bits_as_1_compl_sint(bits).integer.s;
  result += result < 0 ? -1 : 0;
  return (owl_int_op_result) {{result}, owl_true};
}

// calculate integer as offset binary integer
owl_int_op_result owl_bits_as_off_bin_sint(owl_bitarr * bits)
{
  // check params
  if (owl_check_bitarr_all(bits, sizeof(owl_bitarr)) == owl_false)
    return (owl_int_op_result) {{0}, owl_false};
  
  // I can actually just reuse owl_bits_as_uint()
  owl_smax result = ((owl_smax) owl_bits_as_uint(bits).integer.u)
                    - (1 << (bits -> num_bits - 1));
  return (owl_int_op_result) {{result}, owl_true};
}

// calculate integer as power of minus 2 integer
owl_int_op_result owl_bits_as_bminus2_sint(owl_bitarr * bits)
{
  // check params
  if (owl_check_bitarr_all(bits, sizeof(owl_bitarr)) == owl_false)
    return (owl_int_op_result) {{0}, owl_false};
  
  // I can't actually reuse any function! (I think)
  // it is almost a exact copy of the owl_bits_as_uint though
  
  // get the bitarr extra variables
  struct owl_bitarr_extra_vars a = owl_get_bitarr_extra_vars(bits);
  owl_smax result = 0, pow2 = 1;
  // calculate the number
  owl_byte last_bit_mask = 1 << (OWL_BYTE_BITS - 1 - a.last_bit_offset);
  for (owl_umax i = bits -> num_bits; i > 0; i--, pow2 *= -2, owl_circ_bitshift(bits, OWL_BITSHIFT_RIGHT))
    result += (((bits -> arr -> ptr)[a.last_byte_pos] & last_bit_mask) != 0) ? pow2 : 0;
  return (owl_int_op_result) {{result}, owl_true};
}

// get bits as unsigned integer with bit operations 
owl_int_op_result owl_get_uint(owl_bitarr * bits)
{
  // check params
  if (owl_check_bitarr_all(bits, sizeof(owl_bitarr)) == owl_false)
    return (owl_int_op_result) {{0}, owl_false};
  
  // integer to return
  owl_umax result = 0;
  owl_bitarr tmp1 = {&((owl_bytarr) {(void *) &result, sizeof(owl_umax)}),
                     (OWL_BYTE_BITS * sizeof(owl_umax)) - bits -> num_bits,
                     bits -> num_bits};
  // write the bits
  if (owl_write_bits(bits, &tmp1) == owl_false) return (owl_int_op_result) {{0}, owl_false};
  // reverse the bytes on result depending on system endian
  if (OWL_SYS_ENDIAN == OWL_ENDIAN_LITTLE) 
    owl_reverse_bytarr(&((owl_bytarr) {(void *) &result, sizeof(owl_umax)}));
  return (owl_int_op_result) {{result}, owl_true};
}

// get bits as signed integer (2 complement) with bit operations
owl_int_op_result owl_get_sint(owl_bitarr * bits)
{
  // reuse owl_get_uint()
  owl_int_op_result result = owl_get_uint(bits);
  if (result.success == owl_false)
    goto end;
  
  // check if the leftmost bit of the actual integer is 1, if so, add 1s to the left of this bit
  owl_umax leftmost_bit_mask = 1 << (bits -> num_bits - 1);
  if ((result.integer.u & leftmost_bit_mask) != 0)
    while ((leftmost_bit_mask <<= 1) != 0)
      result.integer.u += leftmost_bit_mask;
  // add whatever it is on u to s
  result.integer.s = (owl_smax) result.integer.u;
  
  // done!
  end:
  return result;
}

// write unsigned integer in a bitarr (won't check overflow)
owl_bool owl_write_uint(owl_umax num, owl_bitarr * dest)
{
  // check params
  if (owl_check_bitarr_all(dest, sizeof(owl_bitarr)) == owl_false)
    return owl_false;
    
  // write the bits (big endian no little endian bs)
  owl_bytarr bytarr = {(void *) &num, sizeof(owl_umax)};
  if (OWL_SYS_ENDIAN == OWL_ENDIAN_LITTLE) owl_reverse_bytarr(&bytarr);
  owl_bitarr bitarr = {&bytarr, (sizeof(owl_umax) * OWL_BYTE_BITS) - dest -> num_bits, dest -> num_bits};
  if (owl_write_bits(&bitarr, dest) == owl_false)
    return owl_false;
  
  // done!
  return owl_true;
}

// write signed integer in a bitarr (won't check overflow)
owl_bool owl_write_sint(owl_smax num, owl_bitarr * dest)
{
  // reuse owl_write_uint()
  return owl_write_uint((owl_umax) num, dest);
}

// get bits as unsigned integer
// but they only can be
// - byte aligned
// - multiple of OWL_BYTE_BITS in length
// - little/big endian
owl_int_op_result owl_get_std_uint(owl_bitarr * bits, owl_endian endian)
{
  // check params
  if (owl_check_bitarr_all((void *) bits, sizeof(owl_bitarr)) == owl_false
      || bits -> num_bits % OWL_BYTE_BITS != 0
      || bits -> left_pad % OWL_BYTE_BITS != 0
      || owl_check_endian(endian) == owl_false)
    return (owl_int_op_result) {{0}, owl_false};
  
  // get the integer (modify bits)
  if (endian == OWL_ENDIAN_LITTLE)
    owl_reverse_bytarr(&((owl_bytarr) {bits -> arr -> ptr + (bits -> left_pad / OWL_BYTE_BITS),
                                       bits -> num_bits / OWL_BYTE_BITS}));
  owl_int_op_result result = owl_get_uint(bits);
  if (endian == OWL_ENDIAN_LITTLE) // revert changes to bits
    owl_reverse_bytarr(&((owl_bytarr) {bits -> arr -> ptr + (bits -> left_pad / OWL_BYTE_BITS),
                                       bits -> num_bits / OWL_BYTE_BITS}));
  return result;
}

// same as owl_get_std_uint but for signed integers
owl_int_op_result owl_get_std_sint(owl_bitarr * bits, owl_endian endian)
{
  // reuse owl_get_std_uint()
  owl_int_op_result result = owl_get_std_uint(bits, endian);
  if (result.success == owl_false)
    goto end;
  
  // this function is almost a copy of the owl_get_sint() function
  // the only change was that it calls owl_get_std_uint() instead of owl_get_uint() 
  
  // check if the leftmost bit of the actual integer is 1, if so, add 1s to the left of this bit
  owl_umax leftmost_bit_mask = 1 << (bits -> num_bits - 1);
  if ((result.integer.u & leftmost_bit_mask) != 0)
    while ((leftmost_bit_mask <<= 1) != 0)
      result.integer.u += leftmost_bit_mask;
  // add whatever it is on u to s
  result.integer.s = (owl_smax) result.integer.u;
  
  // done!
  end:
  return result;
}

// write standard unsigned integer into bitarr
owl_bool owl_write_std_uint(owl_umax num, owl_bitarr * dest, owl_endian endian)
{
  // check params
  if (dest == NULL || dest -> left_pad % OWL_BYTE_BITS != 0
      || dest -> num_bits % OWL_BYTE_BITS != 0
      || owl_check_endian(endian) == owl_false)
    return owl_false;
  // reuse owl_write_uint()
  if (owl_write_uint(num, dest) == owl_false)
    return owl_false;
  if (endian == OWL_ENDIAN_LITTLE)
    owl_reverse_bytarr(&((owl_bytarr) {dest -> arr -> ptr + (dest -> left_pad / OWL_BYTE_BITS),
                                       dest -> num_bits / OWL_BYTE_BITS}));
  
  // done!
  return owl_true;
}

// write standard signed integer into bitarr
owl_bool owl_write_std_sint(owl_smax num, owl_bitarr * dest, owl_endian endian)
{
  // reuse owl_write_std_uint()
  return owl_write_std_uint((owl_umax) num, dest, endian);
}

// some code below repeats too much so this function is here to has that
// modifies the bits structure for the next read to the right (resets left_pad)
void owl_mw_upd_vars_internal(owl_bitarr * bits)
{
  bits -> arr -> ptr += (bits -> num_bits + bits -> left_pad) / OWL_BYTE_BITS;
  bits -> arr -> size -= (bits -> num_bits + bits -> left_pad) / OWL_BYTE_BITS;
  bits -> left_pad = 0;
  return;
}

// get std uint and prepare bits to read the next thing right away
owl_int_op_result owl_get_std_uint_mw(owl_bitarr * bits, owl_endian endian)
{
  owl_int_op_result result = owl_get_std_uint(bits, endian);
  if (result.success == owl_true)
    owl_mw_upd_vars_internal(bits);
  return result;
}

// get std sint and prepare bits to read the next thing right away
owl_int_op_result owl_get_std_sint_mw(owl_bitarr * bits, owl_endian endian)
{
  owl_int_op_result result = owl_get_std_sint(bits, endian);
  if (result.success == owl_true)
    owl_mw_upd_vars_internal(bits);
  return result;
}

// get uint8 with moving window
owl_int_op_result owl_get_std_uint8_mw(owl_bitarr * bits, owl_endian endian)
{
  bits -> num_bits = 8;
  return owl_get_std_uint_mw(bits, endian);
}

// get uint16 with moving window
owl_int_op_result owl_get_std_uint16_mw(owl_bitarr * bits, owl_endian endian)
{
  bits -> num_bits = 16;
  return owl_get_std_uint_mw(bits, endian);
}

// get uint32 with moving window
owl_int_op_result owl_get_std_uint32_mw(owl_bitarr * bits, owl_endian endian)
{
  bits -> num_bits = 32;
  return owl_get_std_uint_mw(bits, endian);
}

// get sint8 with moving window
owl_int_op_result owl_get_std_sint8_mw(owl_bitarr * bits, owl_endian endian)
{
  bits -> num_bits = 8;
  return owl_get_std_sint_mw(bits, endian);
}

// get sint16 with moving window
owl_int_op_result owl_get_std_sint16_mw(owl_bitarr * bits, owl_endian endian)
{
  bits -> num_bits = 16;
  return owl_get_std_sint_mw(bits, endian);
}

// get sint32 with moving window
owl_int_op_result owl_get_std_sint32_mw(owl_bitarr * bits, owl_endian endian)
{
  bits -> num_bits = 32;
  return owl_get_std_sint_mw(bits, endian);
}

// writes a std uint into dest and updates dest to write to the next location
owl_bool owl_write_std_uint_mv(owl_umax num, owl_bitarr * dest, owl_endian endian)
{
  if (owl_write_std_uint(num, dest, endian) == owl_true) {
    owl_mw_upd_vars_internal(dest);
    return owl_true;
  }
  return owl_false;
}

// writes a std sint into dest and updates dest to write to the next location
owl_bool owl_write_std_sint_mv(owl_smax num, owl_bitarr * dest, owl_endian endian)
{
  if (owl_write_std_sint(num, dest, endian) == owl_true) {
    owl_mw_upd_vars_internal(dest);
    return owl_true;
  }
  return owl_false;
}

// writes uint8 with moving window 
owl_bool owl_write_std_uint8_mv(owl_umax num, owl_bitarr * dest, owl_endian endian)
{
  dest -> num_bits = 8;
  return owl_write_std_uint_mv(num, dest, endian);
}

// writes uint16 with moving window 
owl_bool owl_write_std_uint16_mv(owl_umax num, owl_bitarr * dest, owl_endian endian)
{
  dest -> num_bits = 16;
  return owl_write_std_uint_mv(num, dest, endian);
}

// writes uint32 with moving window 
owl_bool owl_write_std_uint32_mv(owl_umax num, owl_bitarr * dest, owl_endian endian)
{
  dest -> num_bits = 32;
  return owl_write_std_uint_mv(num, dest, endian);
}

// writes sint8 with moving window 
owl_bool owl_write_std_sint8_mv(owl_smax num, owl_bitarr * dest, owl_endian endian)
{
  dest -> num_bits = 8;
  return owl_write_std_sint_mv(num, dest, endian);
}

// writes sint16 with moving window 
owl_bool owl_write_std_sint16_mv(owl_smax num, owl_bitarr * dest, owl_endian endian)
{
  dest -> num_bits = 16;
  return owl_write_std_sint_mv(num, dest, endian);
}

// writes sint32 with moving window 
owl_bool owl_write_std_sint32_mv(owl_smax num, owl_bitarr * dest, owl_endian endian)
{
  dest -> num_bits = 32;
  return owl_write_std_sint_mv(num, dest, endian);
}
